package timing.ukulele.springdoc;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.*;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import timing.ukulele.springdoc.properties.DocumentProperties;
import timing.ukulele.springdoc.properties.ScopeProperties;
import timing.ukulele.springdoc.properties.ServerProperties;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableConfigurationProperties({DocumentProperties.class, ScopeProperties.class, ServerProperties.class})
@ConditionalOnProperty(
        prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true
)
public class DocumentAutoConfiguration {
    private final DocumentProperties documentProperties;

    public DocumentAutoConfiguration(DocumentProperties documentProperties) {
        this.documentProperties = documentProperties;
    }

    @Bean
    public OpenAPI customOpenAPI() {
        // 基础信息
        Info info = new Info()
                .title(this.documentProperties.getTitle())
                .version(this.documentProperties.getVersion())
                .description(this.documentProperties.getDescription())
                .termsOfService(this.documentProperties.getTermsOfService());
        if (this.documentProperties.getLicense() != null)
            info.license(new License().name(this.documentProperties.getLicense().getName()).url(this.documentProperties.getLicense().getUrl()));
        OpenAPI openAPI = new OpenAPI()
                // 基础描述信息
                .info(info);
        if (this.documentProperties.getEnableOAuth2() != null && this.documentProperties.getEnableOAuth2()) {
            // 组件
            Components components = new Components();
            // 安全认证组件
            SecurityScheme securityScheme = new SecurityScheme();

            // 创建一个oauth认证流程
            OAuthFlows oAuthFlows = new OAuthFlows();
            // 设置OAuth2流程中认证服务的基本信息
            OAuthFlow oAuthFlow = new OAuthFlow()
                    // 授权申请地址
                    .authorizationUrl(this.documentProperties.getAuthorizationUrl())
                    // 获取token地址
                    .tokenUrl(this.documentProperties.getTokenUrl());
            if (!CollectionUtils.isEmpty(this.documentProperties.getScopes())) {
                Scopes scopes = new Scopes();
                this.documentProperties.getScopes().forEach(item -> scopes.addString(item.getName(), item.getDescription()));
                oAuthFlow.scopes(scopes);
            }
            // 使用授权码模式
            oAuthFlows.authorizationCode(oAuthFlow);

            // OAuth2流程
            securityScheme.name("OAuth2认证").flows(oAuthFlows).type(SecurityScheme.Type.OAUTH2);

            // 安全认证名
            String securityName = "Authenticate";
            // 将认证配置加入组件中
            components.addSecuritySchemes(securityName, securityScheme);

            SecurityRequirement securityRequirement = new SecurityRequirement();
            // 将安全认证和swagger-ui关联起来
            securityRequirement.addList(securityName);

            // 添加OAuth2认证流程组件
            openAPI.components(components)
                    // 添加请求时携带OAuth2规范的请求头(通过OAuth2流程获取token后发请求时会自动携带Authorization请求头)
                    .addSecurityItem(securityRequirement);
        }
        if (!CollectionUtils.isEmpty(this.documentProperties.getServers())) {
            List<Server> servers = new ArrayList<>(this.documentProperties.getServers().size());
            this.documentProperties.getServers().forEach(item -> {
                servers.add(new Server().url(item.getUrl()).description(item.getDescription()));
            });
            openAPI.servers(servers);
        }
        return openAPI;
    }
}
